import { ReactElementType } from 'shared/ReactTypes';
import { mountChildFibers, reconcileChildFibers } from './childFibers';
import {
	FiberNode,
	createFiberFromFragment,
	createWorkInProgress,
	createFiberFromOffscreen,
	OffscreenProps
} from './fiber';
import { bailoutHook, renderWithHooks } from './fiberHooks';
import { Lane, NoLanes, includeSomeLanes } from './fiberLanes';
import { processUpdateQueue, UpdateQueue } from './updateQueue';
import {
	ContextProvider,
	Fragment,
	FunctionComponent,
	HostComponent,
	HostRoot,
	HostText,
	MemoComponent,
	OffscreenComponent,
	SuspenseComponent,
	LazyComponent
} from './workTags';
import {
	Ref,
	NoFlags,
	DidCapture,
	Placement,
	ChildDeletion
} from './fiberFlags';
import {
	prepareToReadContext,
	propagateContextChange,
	pushProvider
} from './fiberContext';
import { pushSuspenseHandler } from './suspenseContext';
import { cloneChildFibers } from './childFibers';
import { shallowEqual } from 'shared/shallowEquals';

// 是否能命中bailout
let didReceiveUpdate = false;

export function markWipReceivedUpdate() {
	didReceiveUpdate = true;
}

// 递归中的递阶段
// beginWork 的mount阶段主要做的事
// 生成 子单个子Fiber  或子Fiber链表 如果是当前fiber是 hostRootFiber的子节点的话 就打上插入标记

// update 做的事
// 单节点  看能否复用 fiber 能复用 就拿到 child的 alternate  返回
// 多节点 生成 子链表返回

// 还有做的事 就是将 jsx生成的 reactElement 元素的 props属性 赋值 生成的childFiber的 pendingProps属性
export const beginWork = (wip: FiberNode, renderLane: Lane) => {
	// bailout策略
	didReceiveUpdate = false;
	const current = wip.alternate;

	if (current !== null) {
		const oldProps = current.memoizedProps;
		const newProps = wip.pendingProps;
		// 四要素～ props type
		// {num: 0, name: 'cpn2'}
		// {num: 0, name: 'cpn2'}
		if (oldProps !== newProps || current.type !== wip.type) {
			didReceiveUpdate = true;
		} else {
			// state context
			const hasScheduledStateOrContext = checkScheduledUpdateOrContext(
				current,
				renderLane
			);
			if (!hasScheduledStateOrContext) {
				// 四要素～ state context
				// 命中bailout
				didReceiveUpdate = false;

				switch (wip.tag) {
					case ContextProvider:
						const newValue = wip.memoizedProps.value;
						const context = wip.type._context;
						pushProvider(context, newValue);
						break;
					// TODO Suspense
				}

				return bailoutOnAlreadyFinishedWork(wip, renderLane);
			}
		}
	}

	wip.lanes = NoLanes;

	// 比较，返回子fiberNode 根据 tag 类型 做不同的处理
	switch (wip.tag) {
		case HostRoot:
			return updateHostRoot(wip, renderLane); // 根Fiber
		case HostComponent:
			return updateHostComponent(wip); // 普通的 dom
		case HostText:
			return null; // 文本节点 没有子节点 可以处理 直接 返回null 开始他的 归阶段
		case FunctionComponent:
			return updateFunctionComponent(wip, wip.type, renderLane);
		case Fragment:
			return updateFragment(wip);
		case ContextProvider:
			return updateContextProvider(wip, renderLane);
		case SuspenseComponent:
			return updateSuspenseComponent(wip);
		case OffscreenComponent:
			return updateOffscreenComponent(wip);
		case LazyComponent:
			return mountLazyComponent(wip, renderLane);
		case MemoComponent:
			return updateMemoComponent(wip, renderLane);
		default:
			if (true) {
				console.warn('beginWork未实现的类型');
			}
			break;
	}
	return null;
};

function mountLazyComponent(wip: FiberNode, renderLane: Lane) {
	const LazyType = wip.type;
	const payload = LazyType._payload;
	const init = LazyType._init;
	const Component = init(payload);
	wip.type = Component;
	wip.tag = FunctionComponent;
	const child = updateFunctionComponent(wip, Component, renderLane);
	return child;
}

function updateMemoComponent(wip: FiberNode, renderLane: Lane) {
	// bailout四要素
	// props浅比较
	const current = wip.alternate;
	const nextProps = wip.pendingProps;
	const Component = wip.type.type;

	if (current !== null) {
		const prevProps = current.memoizedProps;

		// state context
		if (!checkScheduledUpdateOrContext(current, renderLane)) {
			// 浅比较props
			if (shallowEqual(prevProps, nextProps) && current.ref === wip.ref) {
				didReceiveUpdate = false;
				wip.pendingProps = prevProps;

				// 满足四要素
				wip.lanes = current.lanes;
				return bailoutOnAlreadyFinishedWork(wip, renderLane);
			}
		}
	}
	return updateFunctionComponent(wip, Component, renderLane);
}

function bailoutOnAlreadyFinishedWork(wip: FiberNode, renderLane: Lane) {
	if (!includeSomeLanes(wip.childLanes, renderLane)) {
		if (true) {
			console.warn('bailout整棵子树', wip);
		}
		return null;
	}

	if (true) {
		console.warn('bailout一个fiber', wip);
	}
	cloneChildFibers(wip);
	return wip.child;
}

function checkScheduledUpdateOrContext(
	current: FiberNode,
	renderLane: Lane
): boolean {
	const updateLanes = current.lanes;

	if (includeSomeLanes(updateLanes, renderLane)) {
		return true;
	}
	return false;
}

function updateContextProvider(wip: FiberNode, renderLane: Lane) {
	const providerType = wip.type;
	const context = providerType._context;
	const newProps = wip.pendingProps;
	const oldProps = wip.memoizedProps;
	const newValue = newProps.value;

	pushProvider(context, newValue);

	if (oldProps !== null) {
		const oldValue = oldProps.value;

		if (
			Object.is(oldValue, newValue) &&
			oldProps.children === newProps.children
		) {
			return bailoutOnAlreadyFinishedWork(wip, renderLane);
		} else {
			propagateContextChange(wip, context, renderLane);
		}
	}

	const nextChildren = newProps.children;
	reconcileChildren(wip, nextChildren);
	return wip.child;
}

function updateFragment(wip: FiberNode) {
	const nextChildren = wip.pendingProps;
	reconcileChildren(wip, nextChildren);
	return wip.child;
}

function updateFunctionComponent(
	wip: FiberNode,
	Component: FiberNode['type'],
	renderLane: Lane
) {
	prepareToReadContext(wip, renderLane);
	// render
	// 执行 函数组件 获取 jsx的编译结果 返回 的 nextChildren 是 一个 ReactElement 元素 或包括 ReactElement 元素 的数组
	// Component 就是 函数本身  jsx 把函数 组件 <app a=1/>编译成 一个  ReactElement 元素 type 属性 为函数
	// 执行 type 传入  wip.pendingProps props
	// nextChildren = Component( wip.pendingProps) wip.pendingProps 就是 a=1 这些参数
	const nextChildren = renderWithHooks(wip, Component, renderLane);

	const current = wip.alternate;
	if (current !== null && !didReceiveUpdate) {
		bailoutHook(wip, renderLane);
		return bailoutOnAlreadyFinishedWork(wip, renderLane);
	}

	reconcileChildren(wip, nextChildren);
	return wip.child;
}

// 处理根Fiber 只需要做两件事

//1. 计算状态的最新值  调用 processUpdateQueue  返回 最新状态 也就是 app 对应的 reactElement 元素
//2. 拿到 这个reactElement 和当前HostRootFiber的child 对比    创造子fiberNode
// 因为hostRootFiber.child  = app 对应的 Fiber

function updateHostRoot(wip: FiberNode, renderLane: Lane) {
	const baseState = wip.memoizedState; // 获取之前的 state 作为基础sate
	const updateQueue = wip.updateQueue as UpdateQueue<Element>; // 获取 消费更新的数据
	const pending = updateQueue.shared.pending; // 获取
	updateQueue.shared.pending = null;

	// 因为在 updateContainer汗水中 给 hostRootFiber 的 updateQueue 存入的 update 的state 就是 app 对应的 reactElement
	// 见updateContainer函数

	// 对于 hostRootFiber 也就是 根Fiber 来说 memoizedState 也就是 传入的 app reactElement 元素
	const prevChildren = wip.memoizedState;

	const { memoizedState } = processUpdateQueue(baseState, pending, renderLane);
	wip.memoizedState = memoizedState;

	const current = wip.alternate;
	// 考虑RootDidNotComplete的情况，需要复用memoizedState
	if (current !== null) {
		if (!current.memoizedState) {
			current.memoizedState = memoizedState;
		}
	}
	// hostRootFiber 取 reactElement 元素 直接 到memoizedState 上取

	const nextChildren = wip.memoizedState;
	if (prevChildren === nextChildren) {
		return bailoutOnAlreadyFinishedWork(wip, renderLane);
	}

	//创建子fiberNode 并赋值给 wip.child
	reconcileChildren(wip, nextChildren);
	return wip.child;
}

// <div id="oDiv" className="test">
// <div>23</div>
// <span>111</span>
// 1111
// </div>
// 生成的 reactElement 大概元素结构

// const a = {
//   $$typeof: Symbol('react.transitional.element'),
// key: null,
// props:{id: 'oDiv', className: 'test', children: [
//   a,a,a
// ]},
// type: "div",
// _owner: null,
// _store: {validated: 0},
// ref: null,
// }

// <div id="oDiv" className="test">
// <div>23</div>
// </div>
// 生成的 reactElement 大概元素结构

// const a = {
//   $$typeof: Symbol('react.transitional.element'),
// key: null,
// props:{id: 'oDiv', className: 'test', children:a},
// type: "div",
// _owner: null,
// _store: {validated: 0},
// ref: null,
// }

// 因为普通dom 没有状态 所以不需要处理更新
// 直接 生成子fiber 就行了
function updateHostComponent(wip: FiberNode) {
	// 这里的 pendingProps  就是 父Fiber的 beginWork 流程里面 根据子 reactElement 元素创建的 子fiber 传入的reactElement.props
	const nextProps = wip.pendingProps;
	const nextChildren = nextProps.children; // 这里的children 就对应的 上面举例的 children  可能是 数组 也可能是 单个 reactElement元素 也 可能就是一个文本
	markRef(wip.alternate, wip);
	reconcileChildren(wip, nextChildren);
	return wip.child;
}

// reconcileChildren函数 的作用是 根据 父fiber 和子 reactElement 元素 生成 子fiberNode
// 如果 单个子节点 children 是 reactElement 元素 或者 文本什么的
// 如果是 多个子节点 children 是数组 数组 里面看情况而定
// 这里的children   可能是 数组 也可能是 单个 reactElement元素 也 可能就是一个文本  这里给 ReactElementType 类型是不对的
function reconcileChildren(wip: FiberNode, children?: ReactElementType) {
	const current = wip.alternate;

	if (current !== null) {
		// update 或 根Fiber的 首次渲染

		// 对于跟Fiber 首次渲染来 来说 wip.alternate 是有值的  指向 自己 的 可以看createWorkInProgress 函数 实现
		//  所以 根fiber 首次渲染的时候 要记录一下 副作用 插入操作
		// 追踪副作用
		// 这里const current = wip.alternate; current?.child 是因为要取 缓冲区的对比
		// 可以看createWorkInProgress 里面取的 alternate 只是 根Fiber的
		// 其他 节点的 每个 Fiber都对应有 alternate 互相指向 而不是 根Fiber 取了就行了

		// current是 工作区的fiberNode
		// wip 是正在协调的 fiberNode
		wip.child = reconcileChildFibers(wip, current?.child, children);
	} else {
		// 除来 根Fiber 的其他节点的 首次渲染 都不记录副作用操作

		//   <div>
		//  <p>练习时长</p>
		//  <span>两年半</span>
		// </div>
		// 理论上mount流程完毕后包含的flags：
		// 两年半 Placement
		// span Placement
		// 练习时长 Placement
		// p Placement
		// div Placement
		// 相比于执行5次Placment，我们可以构建好「离屏DOM树」后，对div执行1次Placement操作

		// mount
		// 这里有个优化
		// 因为首次渲染的时候 对于fiber.tag 为 原生dom 这种类型的节点来说  如果子节点 每个都追踪副作用 就没每个都插入一次
		// 所以不用记录子dom的 副作用 只要创建好一个dom树只之后再插入就行
		// mountChildFibers  这个函数 不 会 追踪副作用
		//  对app节点 执行一次 副作用就行了 将构建好的 app dom树 插入到 root container容器中 就行了
		wip.child = mountChildFibers(wip, null, children);
	}

	// mountChildFibers 和 reconcileChildFibers 函数 都是 ChildReconciler 函数返回的 reconcileChildFibers 函数
	// 只是通过闭包 存储了 是否追踪副作用
}

function markRef(current: FiberNode | null, workInProgress: FiberNode) {
	const ref = workInProgress.ref;

	if (
		(current === null && ref !== null) ||
		(current !== null && current.ref !== ref)
	) {
		workInProgress.flags |= Ref;
	}
}

function updateOffscreenComponent(workInProgress: FiberNode) {
	const nextProps = workInProgress.pendingProps;
	const nextChildren = nextProps.children;
	reconcileChildren(workInProgress, nextChildren);
	return workInProgress.child;
}

function updateSuspenseComponent(workInProgress: FiberNode) {
	const current = workInProgress.alternate;
	const nextProps = workInProgress.pendingProps;

	let showFallback = false;
	const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
	console.log('suspense', 'log');
	if (didSuspend) {
		showFallback = true;
		workInProgress.flags &= ~DidCapture;
	}
	const nextPrimaryChildren = nextProps.children;
	const nextFallbackChildren = nextProps.fallback;
	pushSuspenseHandler(workInProgress);

	if (current === null) {
		if (showFallback) {
			console.log(2, 'suspense');
			return mountSuspenseFallbackChildren(
				workInProgress,
				nextPrimaryChildren,
				nextFallbackChildren
			);
		} else {
			console.log(1, 'suspense');
			return mountSuspensePrimaryChildren(workInProgress, nextPrimaryChildren);
		}
	} else {
		if (showFallback) {
			console.log(4, 'suspense');
			return updateSuspenseFallbackChildren(
				workInProgress,
				nextPrimaryChildren,
				nextFallbackChildren
			);
		} else {
			console.log(3, 'suspense');
			return updateSuspensePrimaryChildren(workInProgress, nextPrimaryChildren);
		}
	}
}

function mountSuspensePrimaryChildren(
	workInProgress: FiberNode,
	primaryChildren: any
) {
	const primaryChildProps: OffscreenProps = {
		mode: 'visible',
		children: primaryChildren
	};
	const primaryChildFragment = createFiberFromOffscreen(primaryChildProps);
	workInProgress.child = primaryChildFragment;
	primaryChildFragment.return = workInProgress;
	return primaryChildFragment;
}

function mountSuspenseFallbackChildren(
	workInProgress: FiberNode,
	primaryChildren: any,
	fallbackChildren: any
) {
	const primaryChildProps: OffscreenProps = {
		mode: 'hidden',
		children: primaryChildren
	};
	const primaryChildFragment = createFiberFromOffscreen(primaryChildProps);
	const fallbackChildFragment = createFiberFromFragment(fallbackChildren, null);
	// 父组件Suspense已经mount，所以需要fallback标记Placement
	fallbackChildFragment.flags |= Placement;

	primaryChildFragment.return = workInProgress;
	fallbackChildFragment.return = workInProgress;
	primaryChildFragment.sibling = fallbackChildFragment;
	workInProgress.child = primaryChildFragment;

	return fallbackChildFragment;
}

function updateSuspensePrimaryChildren(
	workInProgress: FiberNode,
	primaryChildren: any
) {
	const current = workInProgress.alternate as FiberNode;
	const currentPrimaryChildFragment = current.child as FiberNode;
	const currentFallbackChildFragment: FiberNode | null =
		currentPrimaryChildFragment.sibling;

	const primaryChildProps: OffscreenProps = {
		mode: 'visible',
		children: primaryChildren
	};

	const primaryChildFragment = createWorkInProgress(
		currentPrimaryChildFragment,
		primaryChildProps
	);
	primaryChildFragment.return = workInProgress;
	primaryChildFragment.sibling = null;
	workInProgress.child = primaryChildFragment;

	if (currentFallbackChildFragment !== null) {
		const deletions = workInProgress.deletions;
		if (deletions === null) {
			workInProgress.deletions = [currentFallbackChildFragment];
			workInProgress.flags |= ChildDeletion;
		} else {
			deletions.push(currentFallbackChildFragment);
		}
	}

	return primaryChildFragment;
}

function updateSuspenseFallbackChildren(
	workInProgress: FiberNode,
	primaryChildren: any,
	fallbackChildren: any
) {
	const current = workInProgress.alternate as FiberNode;
	const currentPrimaryChildFragment = current.child as FiberNode;
	const currentFallbackChildFragment: FiberNode | null =
		currentPrimaryChildFragment.sibling;

	const primaryChildProps: OffscreenProps = {
		mode: 'hidden',
		children: primaryChildren
	};
	const primaryChildFragment = createWorkInProgress(
		currentPrimaryChildFragment,
		primaryChildProps
	);
	let fallbackChildFragment;

	if (currentFallbackChildFragment !== null) {
		// 可以复用
		fallbackChildFragment = createWorkInProgress(
			currentFallbackChildFragment,
			fallbackChildren
		);
	} else {
		fallbackChildFragment = createFiberFromFragment(fallbackChildren, null);
		fallbackChildFragment.flags |= Placement;
	}
	fallbackChildFragment.return = workInProgress;
	primaryChildFragment.return = workInProgress;
	primaryChildFragment.sibling = fallbackChildFragment;
	workInProgress.child = primaryChildFragment;

	return fallbackChildFragment;
}
